<script lang="ts">
    import { onDestroy, onMount } from "svelte";
    import { Draggable, type DraggableData } from "../Draggable";
    import { useClassList } from "../utils/useClassList";
    import { useStyle } from "../utils/useStyle";
    import type { SliderProps } from "./Slider";
    import { Tooltip } from "../Tooltip";
    import { useFormItemContext } from "../FormItem/FormItem.svelte";

    let { range = false, value = $bindable(range ? [0, 0] : 0), ...props }: SliderProps = $props();
    const ctx = useFormItemContext();
    let rail: any;
    let leftDrag: any;
    let rightDrag = $state<any>();
    let pop: any;
    let popRight = $state<any>();
    let wrap: any;
    const min = props.min ?? 0;
    const max = props.max ?? 100;
    const step = props.step ?? 1.0;
    const classList = $derived(
        useClassList(props, "cm-slider", {
            "cm-slider-disabled": props.disabled,
        }),
    );

    const snap = $derived.by(() => {
        const rect = rail.getBoundingClientRect();
        const allW = rect.width;
        return (allW / (max - min)) * step;
    });

    // 根据值计算位置
    const calculateLeftRight = $derived.by(() => {
        const val: any = range ? value : [min, value];
        const trackWidth = (Math.abs(val[1] - val[0]) / (max - min)) * 100;
        const trackLeft = ((val[0] - min) / (max - min)) * 100;
        const handleRight = ((val[1] - min) / (max - min)) * 100;

        return { left: trackLeft, width: trackWidth, right: handleRight };
    });

    const trackStyle = $derived.by(() => {
        const ret = calculateLeftRight;
        return useStyle({}, { left: ret.left + "%", width: ret.width + "%" });
    });

    const contextLeft = $derived.by(() => {
        const v: any = range ? value[0] : value;
        if (props.tipFormatter) {
            return props.tipFormatter(v);
        }
        return v;
    });

    const contextRight = $derived.by(() => {
        if (props.tipFormatter) {
            return props.tipFormatter(value[1]);
        }
        return value[1];
    });

    // 值改变后同步拖拽点的位置
    $effect(() => {
        updatePosition();
    });

    const toFixed = (num: number) => {
        let r;
        try {
            r = step.toString().split(".")[1].length;
        } catch (e) {
            r = 0;
        }
        const m = Math.pow(10, r);
        return Math.round(num * m) / m;
    };

    //左侧拖拽
    const onLeftDrag = (e: any, data: DraggableData) => {
        const railRect = rail.getBoundingClientRect();
        const allW = railRect.width;
        const v = toFixed((data.x / allW) * (max - min) + min);
        setTimeout(() => {
            pop && pop.updatePosition();
        });
        if (range && v > value[1]) {
            return false;
        }
        const val = range ? [v, Math.max(v, value[1])] : v;
        value = val;
        props.onchange?.(val);
        ctx?.onFieldChange?.();
    };

    //右侧拖拽
    const onRightDrag = (e: any, data: DraggableData) => {
        const railRect = rail.getBoundingClientRect();
        const allW = railRect.width;
        const v = toFixed((data.x / allW) * (max - min) + min);
        setTimeout(() => {
            popRight && popRight.updatePosition();
        });
        if (range && v < value[0]) {
            return false;
        }
        const val = range ? [Math.min(value[0], v), v] : v;
        value = val;
        props.onchange?.(val);
        ctx?.onFieldChange?.();
    };

    // 点击后改变值
    const onMouseDown = (e: any) => {
        if (props.disabled) {
            return;
        }
        if (e.target.classList.contains("cm-slider-handle")) {
            return;
        }
        const slider = e.target.closest(".cm-slider");
        if (!slider) {
            return;
        }
        const sliderRect = slider.getBoundingClientRect();
        const x = e.pageX - sliderRect.left;

        const railRect = rail.getBoundingClientRect();
        const allW = railRect.width;
        const stepNum = Math.round(((x / allW) * (max - min)) / step);
        const v = toFixed(stepNum * step + min);
        // const v = toFixed(Math.round(x / allW * (max - min) / step + min) * step);

        let val = value;

        if (range) {
            const nearLeft = Math.abs(val[1] - v) > Math.abs(val[0] - v);
            val = nearLeft ? [v, val[1]] : [val[0], v];
            value = val;
            setTimeout(() => {
                nearLeft ? pop && pop.updatePosition() : popRight && popRight.updatePosition();
            });
            props.onchange?.(val);
            ctx?.onFieldChange?.();
        } else {
            value = v;
            setTimeout(() => {
                pop && pop.updatePosition();
            });
            props.onchange?.(v);
            ctx?.onFieldChange?.();
        }
    };

    const steps = () => {
        if (!props.marks) {
            return [];
        }
        const arr = [];
        for (let i = min; i <= max; i += step) {
            if (props.marks[i]) {
                arr.push(i);
            }
        }
        return arr;
    };

    const marks = () => {
        if (props.marks) {
            const arr = [];
            for (const step in props.marks) {
                arr.push({
                    step: parseFloat(step),
                    label: props.marks[step],
                });
            }
            return arr;
        }
        return [];
    };

    const updatePosition = () => {
        const ret = calculateLeftRight;
        const rect = rail.getBoundingClientRect();

        const leftX = range ? (rect.width * ret.left) / 100 : (rect.width * ret.right) / 100;
        const rightX = range ? (rect.width * (ret.left + ret.width)) / 100 : 0;

        if (leftDrag) {
            leftDrag.setPosition({
                x: leftX,
                y: 0,
            });
        }
        if (rightDrag) {
            rightDrag.setPosition({
                x: rightX,
                y: 0,
            });
        }
    };

    onMount(() => {
        // 容器尺寸变化的时候改变值的位置
        const ob = new ResizeObserver(() => {
            updatePosition();
        });

        ob.observe(wrap);

        onDestroy(() => ob.disconnect());
    });
</script>

<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class={classList} style={props.style} onmousedown={onMouseDown} bind:this={wrap}>
    <div class="cm-slider-rail" bind:this={rail}></div>
    <div class="cm-slider-track" style={trackStyle}></div>
    <div class="cm-slider-steps">
        {#each steps() as item}
            {@const ranges = range ? value : [min, value]}
            {@const isActive = item >= ranges[0] && item <= ranges[1]}
            {@const stepClass = {
                "cm-slider-step": true,
                "cm-slider-step-active": isActive,
            }}
            {@const left = `left: ${((item - min) / (max - min)) * 100}%`}
            <span class={stepClass} style={left}></span>
        {/each}
    </div>
    <Tooltip disabled={props.disabled} content={contextLeft} align="top" bind:this={pop} arrow>
        <Draggable axis="x" disabled={props.disabled} bind:this={leftDrag} ondrag={onLeftDrag} bounds="parent" class="cm-slider-handle-drag" grid={[snap, snap]}>
            <div class="cm-slider-handle" tabIndex="0"></div>
        </Draggable>
    </Tooltip>

    {#if range}
        <Tooltip disabled={props.disabled} content={contextRight} align="top" bind:this={popRight} arrow>
            <Draggable axis="x" disabled={props.disabled} bind:this={rightDrag} ondrag={onRightDrag} bounds="parent" class="cm-slider-handle-drag" grid={[snap, snap]}>
                <div class="cm-slider-handle" tabIndex="0"></div>
            </Draggable>
        </Tooltip>
    {/if}
    {#if props.marks}
        <div class="cm-slider-marks">
            {#each marks() as item}
                {@const left = `left: ${((item.step - min) / (max - min)) * 100}%`}
                <span class="cm-slider-mark" style={left}>{item.label}</span>
            {/each}
        </div>
    {/if}
</div>
